package org.geek.week1.homework2;


import org.geek.week1.Util;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author wangtaoqi
 * @create 2021/9/16
 */
public class HelloClassLoaderChange extends ClassLoader {

    private final String classPrefix = ".xlass";
    public static final String path = "/Hello.xlass.zip";
    static AtomicReference<HelloClassLoaderChange>       reference = new AtomicReference<>(new HelloClassLoaderChange());
    private MonitorThread monitorThread = new MonitorThread(new MonitorRunnable());

    public static   class   MonitorRunnable implements   Runnable{
        private long modify=-1L;
        @Override
        public void run() {
            final Thread thread = Thread.currentThread();
            final URL resource = getClass().getResource(path);
            final String path;
            try {
                path = URLDecoder.decode(resource.getFile(), "utf-8");
                File file = new File(path);
                while (!thread.isInterrupted()) {
                    final long l = file.lastModified();
                    if (modify != l) {
                        if (modify != -1) {
                            System.out.println("发现改变 " + path);
                            reference .set(new HelloClassLoaderChange());
                            System.gc();
                            break ;
                        }
                        modify = l;
                        TimeUnit.SECONDS.sleep(1);

                    }
                }
            } catch (UnsupportedEncodingException | InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //监听守护线程
    private static class MonitorThread extends Thread {

        public MonitorThread(Runnable runnable) {
            super(runnable);
            this.setDaemon(true);
        }
    }

    public HelloClassLoaderChange() {
        monitorThread.start();
    }

    private byte[] readFile(InputStream inputStream) {
        byte[] b = null;
        try (BufferedInputStream stream = new BufferedInputStream(inputStream)) {
            b = new byte[stream.available()];
            stream.read(b, 0, b.length);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return b;
    }

    private byte[] readJar(String name) {
        try {
            final String findName = dir2Package(name) + classPrefix;
            final URL resourceAsStream = getClass().getResource(path);
            final String decode = Util.textDecode(resourceAsStream.getFile());
            JarFile jarFile = new JarFile(decode);
            JarEntry jarEntry1 = jarFile.getJarEntry(findName);
            final InputStream inputStream = jarFile.getInputStream(jarEntry1);
            return readFile(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (Objects.isNull(name)) {
            return null;
        }
        byte[] fileBytes = readJar(name);
        final byte[] originalBytes = restore(fileBytes);
        final Class<?> target = super.defineClass(name, originalBytes, 0, originalBytes.length);
        printMethodInfo(target);
        return target;
    }

    private static byte[] restore(byte[] bytes) {
        byte originalByte[] = new byte[bytes.length];
        for (int i = 0; i < bytes.length; i++) {
            final byte b = bytes[i];
            originalByte[i] = (byte) (255 - b);
        }
        return originalByte;
    }

    private String dir2Package(String name) {
        return name.replace(".", "/");
    }

    private static void printMethodInfo(Class c) {
        if (c == null) {
            return;
        }
        final Method[] declaredMethods = c.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.printf("方法名:%s 参数个数:%s", declaredMethod.getName(), declaredMethod.getParameterCount());
            StringJoiner paramJoin = new StringJoiner(",");
            final Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
            if (parameterTypes != null) {
                for (Class<?> parameterType : parameterTypes) {
                    paramJoin.add(parameterType.getName());
                }
            }
            System.out.printf("参数:%s\n", paramJoin);
        }
    }

    private static Object run() throws Exception {
        final String findClassName = "Hello";
        final String findMethodName = "hello";
        final Class<?> helloClass = reference.get().loadClass(findClassName);
        final Object hello = helloClass.newInstance();
        Method methodHello = hello.getClass().getMethod(findMethodName, null);
        methodHello.invoke(hello, null);
        return hello;
    }

    private static void exec() {
        try {
            Object run = run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        Thread execThread=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                try {
                    exec();
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });
        execThread.start();
    }
}
